﻿using System;
using System.Collections.Generic;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Highlighting;
using System.IO;
using System.Xml;
using ICSharpCode.AvalonEdit.CodeCompletion;
using ICSharpCode.AvalonEdit.Folding;
using System.Windows.Input;
using System.Windows.Threading;
using ICSharpCode.AvalonEdit.Search;
using System.Windows;
using System.Text;
using System.Windows.Data;
using SHomeWorkshop.LunarMind;

namespace LunarSF.SHomeWorkshop.LunarMind
{
    /// <summary>
    /// 创建者：杨震宇
    /// 创建时间：2013年3月29日
    /// 
    /// 说明：试题编辑器类。
    /// </summary>
    public class QuestionEditor : TextEditor
    {
        private static IHighlightingDefinition customHighlighting;

        static QuestionEditor()
        {
            // 载入代码高亮的定义文件
            Stream s = typeof(MainWindow).Assembly.GetManifestResourceStream("SHomeWorkshop.LunarMind.TestEditor.CustomHighlighting.xshd");
            if (s != null)
            {
                using (XmlReader reader = new XmlTextReader(s))
                {
                    customHighlighting = ICSharpCode.AvalonEdit.Highlighting.Xshd.HighlightingLoader.Load(reader, HighlightingManager.Instance);
                }
            }
        }

        public QuestionEditor()
        {
            this.WordWrap = true;
            this.ShowLineNumbers = true;
            this.Options.ShowColumnRuler = true;
            this.Options.ShowSpaces = true;
            this.Options.ShowTabs = true;
            this.Options.WordWrapIndentation = this.FontSize * 8;
            this.Options.HighlightCurrentLine = true;

            //括弧替换
            //this.PreviewKeyDown += QuestionEditor_PreviewKeyDown;
            this.PreviewTextInput += QuestionEditor_PreviewTextInput;

            #region 代码高亮处理
            HighlightingManager.Instance.RegisterHighlighting("Custom Highlighting", new string[] { ".LunarTest" }, customHighlighting);
            this.SyntaxHighlighting = HighlightingManager.Instance.GetDefinitionByExtension(".LunarTest");
            this.TextArea.IndentationStrategy = new ICSharpCode.AvalonEdit.Indentation.DefaultIndentationStrategy();

            #endregion

            #region 代码折叠处理Folding
            foldingStrategy = new CustomFoldingStrategy(this);
            foldingManager = FoldingManager.Install(this.TextArea);
            foldingStrategy.UpdateFoldings(foldingManager, this.Document);

            foldingUpdateTimer.Interval = TimeSpan.FromSeconds(0.05);//50毫秒。
            foldingUpdateTimer.Tick += foldingUpdateTimer_Tick;
            foldingUpdateTimer.Start();
            #endregion

            #region 自动完成功能
            this.TextArea.TextEntered += TextArea_TextEntered;
            this.TextArea.TextEntering += TextArea_TextEntering;
            #endregion

            //this.TextChanged += QuestionEditor_TextChanged;

            //搜索框

            // 下面这两行是 avalonedit 5.0 版的写法。
            //searchPanel = new SearchPanel() { FontSize = Globals.MainWindow.FontSize, };
            //searchPanel.Attach(this.TextArea);

            searchPanel = SearchPanel.Install(this.TextArea);
            searchPanel.FontSize = Globals.MainWindow.FontSize;

            //初始文本必须是未修改状态。
            this.Document.UndoStack.MarkAsOriginalFile();
        }

        private SearchPanel searchPanel;

        public void CloseSearchPanel()
        {
            searchPanel.Close();
        }

        public void Find()
        {
            if (searchPanel.IsClosed)
            {
                searchPanel.Open();
            }

            searchPanel.SearchPattern = this.TextArea.Selection.GetText();

            searchPanel.Reactivate();
        }

        public void FindPreview()
        {
            searchPanel.FindPrevious();
        }

        public void FindNext()
        {
            searchPanel.FindNext();
        }

        //void QuestionEditor_TextChanged(object sender, EventArgs e)
        //{
        //    if (this.Document.UndoStack.IsOriginalFile == false)
        //        this.IsModified = true;//勉强解决。
        //    else
        //        this.IsModified = false;
        //}

        void QuestionEditor_PreviewTextInput(object sender, TextCompositionEventArgs e)
        {
            switch (e.Text)
            {
                case "[":
                case "［":
                case "〖":
                    {
                        this.Document.Replace(this.SelectionStart, this.SelectedText.Length, "【", ICSharpCode.AvalonEdit.Document.OffsetChangeMappingType.RemoveAndInsert);
                        this.Select(this.SelectionStart, 0);

                        e.Handled = true;
                        return;
                    }
                case "]":
                case "］":
                case "〗":
                    {
                        this.Document.Replace(this.SelectionStart, this.SelectedText.Length, "】", ICSharpCode.AvalonEdit.Document.OffsetChangeMappingType.RemoveAndInsert);
                        this.Select(this.SelectionStart, 0);

                        //this.Document.Insert(this.CaretOffset, "】");
                        e.Handled = true;
                        return;
                    }
            }
        }

        //用这个办法来替换方括弧存在一个问题：当中文输入法被打开时，它不起作用！！！
        //void QuestionEditor_PreviewKeyDown(object sender, KeyEventArgs e)
        //{
        //    KeyStates ksLShift = Keyboard.GetKeyStates(Key.LeftShift);
        //    KeyStates ksRShift = Keyboard.GetKeyStates(Key.RightShift);
        //    KeyStates ksLAlt = Keyboard.GetKeyStates(Key.LeftAlt);
        //    KeyStates ksRAlt = Keyboard.GetKeyStates(Key.RightAlt);
        //    KeyStates ksLCtrl = Keyboard.GetKeyStates(Key.LeftCtrl);
        //    KeyStates ksRCtrl = Keyboard.GetKeyStates(Key.RightCtrl);

        //    bool isCtrl, isShift, isAlt;

        //    isShift = (ksLShift & KeyStates.Down) > 0 || (ksRShift & KeyStates.Down) > 0;
        //    isCtrl = (ksLCtrl & KeyStates.Down) > 0 || (ksRCtrl & KeyStates.Down) > 0;
        //    isAlt = (ksLAlt & KeyStates.Down) > 0 || (ksRAlt & KeyStates.Down) > 0;

        //    if (isShift || isCtrl || isAlt) return;//不替换。

        //    if (e.Key == Key.OemOpenBrackets)
        //    {
        //        this.Document.Insert(this.CaretOffset, "【");
        //        e.Handled = true;
        //    }
        //    else if (e.Key == Key.OemCloseBrackets)
        //    {
        //        this.Document.Insert(this.CaretOffset, "】");
        //        e.Handled = true;
        //    }
        //}

        #region 自动完成

        CompletionWindow completionWindow;

        void TextArea_TextEntering(object sender, TextCompositionEventArgs e)
        {
            //if (e.Text == ".")
            //{
            // Open code completion after the user has pressed dot:

            var line = this.Document.GetLineByOffset(this.CaretOffset);
            if (line == null) return;
            if (this.CaretOffset - line.Offset > 3) return;//只在行首几列才自动完成——本例不考虑其它情况。

            if (completionWindow == null)
            {
                completionWindow = new CompletionWindow(this.TextArea)
                {
                    MinHeight = 34,
                };

                IList<ICompletionData> data = completionWindow.CompletionList.CompletionData;

                var st = new CustomCompletionData("试题＞＞", "每个试题都应以此开头。", "　　试题＞＞");
                var da = new CustomCompletionData("答案＞＞", "每个答案都应以此为开头。", "　　答案＞＞");//缩进不采用Tab是因为易乱。
                var cx = new CustomCompletionData("错项＞＞", "每个备选的错项都应以此为开头。", "　　错项＞＞");
                var jx = new CustomCompletionData("解析＞＞", "每个解析条目都应以此开头。", "　　解析＞＞");
                var cl = new CustomCompletionData("＜＜材料＞＞", "主观题每则材料均应以此开头。", "＜＜材料＞＞");
                var cc = new CustomCompletionData("＜＜出处＞＞", "主观题每则材料的出处。", "＜＜出处＞＞");
                var wt = new CustomCompletionData("＜＜问题＞＞", "问题集中可包含多个问题。", "＜＜问题＞＞");

                var wb = new CustomCompletionData("＝=〓=＝", "最后一道试题的结尾添加此符号才能折叠，其它试题不需要添加。", "〓〓〓〓〓〓");//这样设计是保证全角等于号与半角等于号都有用。
                var xx = new CustomCompletionData("<<<信息＞＜信息>>>", "每个文档开头应以指明文档相关信息，如章节、创建者、创建时间等等。",
                    "＜＜＜信息＞\r\n" +
                    "　类型＞＞＜＜类型\r\n" +
                    "　标题＞＞＜＜标题\r\n" +
                    "　日期＞＞＜＜日期\r\n" +
                    "　辑录＞＞＜＜辑录\r\n" +
                    "　电邮＞＞＜＜电邮\r\n" +
                    "　备注＞＞＜＜备注\r\n" +
                    "＜信息＞＞＞\r\n\r\n");

                st.Completed += Item_Completed;
                da.Completed += Item_Completed;
                cx.Completed += Item_Completed;
                jx.Completed += Item_Completed;
                cl.Completed += Item_Completed;
                cc.Completed += Item_Completed;
                wt.Completed += Item_Completed;
                wb.Completed += Item_Completed;
                xx.Completed += xx_Completed;

                data.Add(st);
                data.Add(da);
                data.Add(cx);
                data.Add(jx);
                data.Add(cl);
                data.Add(cc);
                data.Add(wt);
                //data.Add(new MyCompletionData("【方括弧对】", "试题中应包含一个或多个填空项目，这些填空项用一对方括弧包围。","【】"));//效果不好。
                data.Add(wb);
                data.Add(xx);
            }

            completionWindow.Show();
            completionWindow.Closed += delegate
            {
                completionWindow = null;
            };

            //}
        }

        void xx_Completed(object sender, EventArgs e)
        {
            this.Select(this.SelectionStart - 60, 0);
        }

        void Item_Completed(object sender, EventArgs e)
        {
            //var cData = sender as MyCompletionData;

            //这个功能本是用来控制输入法，但效果不好，会让用户无所适从，首拼解决不了太多问题，例如：直接输入“试题”二字并不比输入“st”来得慢。
            //Globals.SwitchInputMethod(true);
        }

        void TextArea_TextEntered(object sender, TextCompositionEventArgs e)
        {
            if (e.Text.Length > 0 && completionWindow != null)
            {
                if (!char.IsLetterOrDigit(e.Text[0]) && e.Text[0] != '=' && e.Text[0] != '＝')//不屏蔽等号会导致一闪而过，虽然方便，但会难以理解。
                {
                    // Whenever a non-letter is typed while the completion window is open,
                    // insert the currently selected element.
                    completionWindow.CompletionList.RequestInsertion(e);

                    //这个功能本是用来控制输入法，但效果不好，会让用户无所适从，首拼解决不了太多问题，例如：直接输入“试题”二字并不比输入“st”来得慢。
                    //if (e.Text == "\n" || e.Text == "\r" || e.Text == "\r\n")
                    //{
                    //    Globals.SwitchInputMethod(false);
                    //}
                    //else
                    //{
                    //    Globals.SwitchInputMethod(true);
                    //}
                }
            }

            //else//这个功能本是用来控制输入法，但效果不好，会让用户无所适从，首拼解决不了太多问题，例如：直接输入“试题”二字并不比输入“st”来得慢。
            //{
            //    if (e.Text == "\n" || e.Text == "\r" || e.Text == "\r\n")
            //    {
            //        Globals.SwitchInputMethod(false);
            //    }
            //}

            // Do not set e.Handled=true.
            // We still want to insert the character that was typed.
        }

        #endregion

        #region 代码折叠

        void foldingUpdateTimer_Tick(object sender, EventArgs e)
        {
            if (foldingStrategy != null)
            {
                foldingStrategy.UpdateFoldings(foldingManager, this.Document);
            }
        }

        DispatcherTimer foldingUpdateTimer = new DispatcherTimer();

        /// <summary>
        /// 提供这个属性是为了便于在选项卡“不在最前”时停止折叠计算，减少消耗。
        /// </summary>
        public DispatcherTimer FoldingUpdateTimer { get { return this.foldingUpdateTimer; } }

        FoldingManager foldingManager;
        AbstractFoldingStrategy foldingStrategy;

        #endregion

        /// <summary>
        /// 尝试根据试题文本判断其中有几个填空项，然后根据这些填空项自动插入一些“答案＞＞”“错项＞＞”“解析＞＞”之类的标签。
        /// </summary>
        public void InsertChoiceQuestionTags()
        {
            //if (this.SelectionLength <= 0)
            //{
            //    SelectLine();
            //}

            //选择区域所在的行都选中
            var startLine = this.Document.GetLineByOffset(this.SelectionStart);
            var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
            this.Select(startLine.Offset, endLine.EndOffset - startLine.Offset);

            //要求选中题干文本（全部）
            var text = this.SelectedText.Replace("\r\n", "").Replace("\t", "").Replace(" ", "").Replace("　", "");
            if (text.StartsWith("试题＞＞") == false)
            {
                MessageBox.Show("　　请选中要生成标签的试题文本，这些文本中应至少有一个用方括弧对标记出来的填充项。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            int fillblankCount = 0;
            StringBuilder errorMsg = new StringBuilder();

            if (QuestionValidateManager.ValidatePairOfBrackets(text, ref fillblankCount, true, errorMsg) == false)
            {
                MessageBox.Show("　　选中的文本格式存在问题，无法自动生成标签集。错误信息如下：\r\n" + errorMsg.ToString(),
                   Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            char[] splitterLeft = new char[1];
            splitterLeft[0] = '【';

            char[] splitterRight = new char[1];
            splitterRight[0] = '】';

            string[] arrayLeft = text.Split(splitterLeft, StringSplitOptions.RemoveEmptyEntries);

            List<string> answers = new List<string>();

            foreach (string s in arrayLeft)
            {
                if (s.Contains("】") == false) continue;

                int index = s.IndexOf('】');
                if (index < 0) continue;

                string answer = s.Substring(0, index);
                answers.Add(answer);
            }

            StringBuilder tagsText = new StringBuilder();

            int fstAnswerLength = -1;

            tagsText.Append("\r\n\t\t\t\r\n");
            foreach (var s in answers)
            {
                if (fstAnswerLength < 0)
                {
                    fstAnswerLength = s.Length;
                }

                tagsText.Append("　　答案＞＞" + s + "\r\n");
                tagsText.Append("　　解析＞＞\r\n\t\t\t\r\n");

                //默认三个错项。
                tagsText.Append("　　错项＞＞\r\n");
                tagsText.Append("　　解析＞＞\r\n\t\t\t\r\n");
                tagsText.Append("　　错项＞＞\r\n");
                tagsText.Append("　　解析＞＞\r\n\t\t\t\r\n");
                tagsText.Append("　　错项＞＞\r\n");
                tagsText.Append("　　解析＞＞\r\n\t\t\t\r\n");
            }
            tagsText.Append("〓〓〓〓〓〓\r\n\r\n");//空一行，顶格，方便立即开始编辑新试题

            var line = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength - 1);//这个长度肯定大于0。

            int oldEndOffset = line.EndOffset;

            this.Document.Insert(line.EndOffset, tagsText.ToString());
            this.Select(oldEndOffset + fstAnswerLength + 34, 0);
        }

        /// <summary>
        /// 以选择区起始位置为准，选择所在的整行文本。
        /// </summary>
        internal void SelectLine()
        {
            if (this.SelectionStart >= 0 && this.SelectionStart <= this.Document.TextLength)
            {
                var line = this.Document.GetLineByOffset(this.SelectionStart);
                if (line != null)
                {
                    this.Select(line.Offset, line.EndOffset - line.Offset);
                }
            }
        }

        internal void InsertJudgeQuestionTags(string judgeValue = "正确")
        {
            //选择区域所在的行都选中
            var startLine = this.Document.GetLineByOffset(this.SelectionStart);
            var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
            this.Select(startLine.Offset, endLine.EndOffset - startLine.Offset);

            //要求选中题干文本（全部）
            var text = this.SelectedText.Replace("\r\n", "").Replace("\t", "").Replace(" ", "").Replace("　", "");
            if (text.StartsWith("试题＞＞") == false)
            {
                MessageBox.Show("　　请选中要生成标签的试题文本，这些文本中不应包括方括弧【和】，因为方括弧对包括的文本会被当成填充项。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (text.Contains("【") || text.Contains("】"))
            {
                MessageBox.Show("　　选中的文本格式存在问题，无法自动生成判断题标签集。错误信息如下：\r\n" +
                    "判断题题干中不应包括方括号【和】，被方括号包围的文本会被当成填空项来处理。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            //在末尾加上【正确】或【错误】。
            text += "【" + judgeValue + "】";

            char[] splitterLeft = new char[1];
            splitterLeft[0] = '【';

            char[] splitterRight = new char[1];
            splitterRight[0] = '】';

            string[] arrayLeft = text.Split(splitterLeft, StringSplitOptions.RemoveEmptyEntries);

            List<string> answers = new List<string>();

            foreach (string s in arrayLeft)
            {
                if (s.Contains("】") == false) continue;

                int index = s.IndexOf('】');
                if (index < 0) continue;

                string answer = s.Substring(0, index);
                answers.Add(answer);
            }

            StringBuilder tagsText = new StringBuilder();

            int fstAnswerLength = -1;

            tagsText.Append("【" + judgeValue + "】\r\n\t\t\t\r\n");
            foreach (var s in answers)
            {
                if (fstAnswerLength < 0)
                {
                    fstAnswerLength = s.Length;
                }

                tagsText.Append("　　答案＞＞" + judgeValue + "\r\n");
                tagsText.Append("　　解析＞＞\r\n\t\t\t\r\n");

                //默认三个错项。
                tagsText.Append("　　错项＞＞" + (judgeValue == "正确" ? "错误" : "正确") + "\r\n");
                tagsText.Append("　　解析＞＞\r\n\t\t\t\r\n");
            }
            tagsText.Append("〓〓〓〓〓〓\r\n\r\n");//空一行，顶格，方便立即开始编辑新试题

            var line = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength - 1);//这个长度肯定大于0。

            int oldEndOffset = line.EndOffset;

            this.Document.Insert(line.EndOffset, tagsText.ToString());
            this.Select(oldEndOffset + fstAnswerLength + 34, 0);
        }
    }
}
